//
//  TransformObservableViewController.swift
//  Example
//
//  Created by T AO on 2022/2/9.
//

import UIKit
import RxSwift

class TransformObservableViewController: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

//        buffer()
        window()
//        map()
//        flatMap()
//        flatMapLatest()
//        concatMap()
//        scan()
//        groupBy()
    }

}

// MARK: - 映射操作符

extension TransformObservableViewController {
    /// buffer
    /// 缓冲组合
    private func buffer() {
        let subject = PublishSubject<String>()
        
        /// 1秒缓冲3个一起以数组方式抛出，如果缓存不足3个，也会发出
        subject
            .buffer(timeSpan: RxTimeInterval.seconds(1), count: 3, scheduler: MainScheduler.instance)
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        
        subject.onNext("1")
        subject.onNext("2")
//        subject.onNext("3")
        
//        subject.onNext("a")
//        subject.onNext("b")
//        subject.onNext("c")
    }
    
    /// window
    /// 和 buffer 十分相似。不过 buffer 是周期性的将缓存的元素集合发送出来，而 window 周期性的将元素集合以 Observable 的形态发送出来。
    /// 同时 buffer 要等到元素搜集完毕后，才会发出元素序列。而 window 可以实时发出元素序列
    private func window() {
        let subject = PublishSubject<String>()
        
        // 每3个元素作为一个子 Observable 发出
        subject
            .window(timeSpan: RxTimeInterval.seconds(1), count: 3, scheduler: MainScheduler.instance)
            .subscribe(onNext: { [weak self] in
                print("subscribe: \($0)")
                guard let self = self else { return }
                $0.asObservable()
                    .subscribe(onNext: { print($0) })
                    .disposed(by: self.disposeBag)
            })
            .disposed(by: disposeBag)
        
        subject.onNext("a")
        subject.onNext("b")
//        subject.onNext("c")
        
        print("Resources.total ", RxSwift.Resources.total)
        
//        subject.onNext("1")
//        print("Resources.total ", RxSwift.Resources.total)
        
//        subject.onNext("2")
//        subject.onNext("3")
//        print("Resources.total ", RxSwift.Resources.total)
    }
    
    /// map
    /// 通过传入一个函数闭包把原来的 Observable 序列转变为一个新的 Observable 序列。
    /// 相当于返回一个 2倍 的新序列
    private func map() {
        Observable.of(1, 2, 3, 4)
            .map { $0 * 2 }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }
    
    /// flatMap
    /// map 在做转换的时候容易出现“升维”的情况。即转变之后，从一个序列变成了一个序列的序列。
    /// 而 flatMap 操作符会对源 Observable 的每一个元素应用一个转换方法，将他们转换成 Observables。 然后将这些 Observables 的元素合并之后再发送出来。即又将其 "拍扁"（降维）成一个 Observable 序列。
    /// 这个操作符是非常有用的。比如当 Observable 的元素本生拥有其他的 Observable 时，我们可以将所有子 Observables 的元素发送出来。
    private func flatMap() {
        let boy = TTPlayer(score: 100)
        let girl = TTPlayer(score: 90)
        let other = TTPlayer(score: 1)
        
        // BehaviorSubject：订阅后，获取Subject发出的最新值，然后是订阅后发出的值
        let player = BehaviorSubject(value: boy)
        
        player.asObservable()
            .flatMap { $0.score.asObservable() }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        
        boy.score.onNext(60)
        boy.score.onNext(80)
        
        player.onNext(girl) // 90 50 40
        
        boy.score.onNext(50)
        boy.score.onNext(40)
        
        player.onNext(boy) // 40 10 0
        
        girl.score.onNext(10)
        girl.score.onNext(0)
        
        player.onNext(other)
        
        other.score.onNext(2)
        
        girl.score.onNext(20)
    }
    
    /// flatMapLatest
    /// flatMapLatest 与 flatMap 的唯一区别是：flatMapLatest 只会接收最新的 value 事件
    private func flatMapLatest() {
        let boy = TTPlayer(score: 100)
        let girl = TTPlayer(score: 90)
        
        let player = BehaviorSubject(value: boy)
        
        player.asObservable()
            .flatMapLatest { $0.score.asObservable() }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        
        boy.score.onNext(60)
        
        player.onNext(girl)
        
        boy.score.onNext(50) // 不打印
        
        girl.score.onNext(10)
        girl.score.onNext(0)
    }
    
    /// concatMap
    /// concatMap 与 flatMap 的唯一区别是：当前一个 Observable 元素发送完毕后，后一个Observable 才可以开始发出元素。或者说等待前一个 Observable 产生完成事件后，才对后一个 Observable 进行订阅。
    /// 如果 subject1 不发送 onCompleted，subject2 永远不会输出
    private func concatMap() {
        let subject1 = BehaviorSubject(value: "A")
        let subject2 = BehaviorSubject(value: "1")
        
        let subject = BehaviorSubject(value: subject1)
        
        subject
            .concatMap { $0 }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        
        subject1.onNext("B")
        
        subject.onNext(subject2)
        
        subject2.onNext("2")
        
        subject1.onNext("C") // ABC
        
        subject1.onCompleted() // 只有 subject1 序列结束后，才能接收 subject2 序列，打印 2
    }
    
    /// scan
    /// 先给一个初始化的数，然后不断的拿前一个结果和最新的值进行处理操作
    private func scan() {
        Observable.of(10, 100, 1000)
            .scan(2) { aggregateValue, newValue in
                aggregateValue + newValue // 10 + 2, 100 + 10 + 2, 1000 + 100 + 10 + 2
            }
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }
    
    /// groupBy
    /// 将源 Observable 分解为多个子 Observable，然后将这些子 Observable 发送出来。
    /// 也就是说该操作符会将元素通过某个键进行分组，然后将分组后的元素序列以 Observable 的形态发送出来。
    /// 将奇数偶数分成两组
    private func groupBy() {
        Observable<Int>.of(0, 1, 2, 3, 4, 5)
            .groupBy { num -> String in
                return num % 2 == 0 ? "偶数" : "奇数"
            }
            .subscribe { event in
                switch event {
                case .next(let group):
                    group.asObservable().subscribe { event in
                        print("key: \(group.key) event: \(event)")
                    }.disposed(by: self.disposeBag)
                default:
                    print("...")
                }
            }
            .disposed(by: disposeBag)
    }
}
